/*
 * Copyright (c) Kumo Inc. and affiliates.
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <type_traits>

#include <melon/traits.h>

namespace melon {
    template<typename... Holders>
    class any_badge;

    /**
     * Badge pattern allows us to abstract over friend classes and make friend
     * feature more scoped. Using this simple technique we can specify a badge tag
     * on specific functions we want to gate for particular caller contexts (badge
     * holders). Badge can only be constructed by the specified badge holder binding
     * the tagged functions to that call site.
     *
     * As a rule, it is poor form to pass a badge to arbitrary code. It is poor form
     * for virtual, type-erased, or template functions to accept badges or to be
     * passed badges.
     *
     * Example:
     *   class ProtectedClass: {
     *     ...
     *    public:
     *     ...
     *     static void func(melon::badge<FriendClass>);
     *   };
     *
     *   void FriendClass::callProtectedFunc() {
     *     ProtectedClass::func({});               // compiles
     *   }
     *   void OtherClass::callProtectedFunc() {
     *     ProtectedClass::func({});               // does not compile!
     *   }
     *
     *     See: https://awesomekling.github.io/Serenity-C++-patterns-The-Badge/
     *
     */
    template<typename Holder>
    class badge {
    public:
        friend Holder;
        /* implicit */
        constexpr badge(any_badge<Holder>) noexcept {
        }

    private:
        /* implicit */
        constexpr badge() noexcept {
        }
    };

    /**
     * For cases when multiple badge holders need to call a function we can
     * use melon::any_badge over each individual holder allowed.
     * We allow subsets of badges to lift into supersets:
     * melon::any_badge<A, B> lifts into melon::any_badge<A, B, C>.
     *
     * Example:
     *   class ProtectedClass: {
     *    public:
     *     static void func(melon::any_badge<FriendClass, OtherFriendClass>);
     *    };
     *
     *   void FriendClass::callProtectedFunc() {
     *     ProtectedClass::func(melon::badge<FriendClass>{});      // compiles
     *   }
     *   void OtherFriendClass::callProtectedFunc() {
     *     ProtectedClass::func(melon::badge<OtherFriendClass>{}); // compiles
     *   }
     */
    template<typename... Holders>
    class any_badge {
    public:
        template<
            typename Holder,
            typename = std::enable_if_t<melon::IsOneOf<Holder, Holders...>::value> >
        /* implicit */ constexpr any_badge(badge<Holder>) noexcept {
        }

        // Fedora 34 and 35 ship a gcc with the following regression:
        // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=104008
#if __GNUC__ == 11 && __GNUC_MINOR__ <= 2
  template <typename... OtherHolders>
  /* implicit */ constexpr any_badge(any_badge<OtherHolders...>) noexcept {}
#else
        template<
            typename... OtherHolders,
            typename = std::enable_if_t<melon::StrictConjunction<
                melon::IsOneOf<OtherHolders, Holders...>...>::value> >
        /* implicit */ constexpr any_badge(any_badge<OtherHolders...>) noexcept {
        }
#endif
    };
} // namespace melon
